﻿using System;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Json;
using System.Security.Permissions;
using System.Text;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.UI;

[assembly: System.Web.UI.WebResource("AjaxAvailableUserControls.Library.Scripts.jquery-json.js", "text/javascript")]
[assembly: System.Web.UI.WebResource("AjaxAvailableUserControls.Library.Scripts.core.js", "text/javascript")]

namespace AjaxAvailableUserControls.Library.Logic
{
    /// <summary>
    /// Base class for all user controls contained ajax logic
    /// </summary>
    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public class AjaxAvailableUserControl : UserControl, ICallbackEventHandler
    {

        #region events
        /// <summary>
        /// This event vill be raised when server get data from the client side
        /// </summary>
        [Browsable(true)]
        [Category("Callback Handlers")]
        [Description("This event vill be raised when server get data from the client side")]
        public event EventHandler<ClientDataReceivedEventArgs> ClientDataReceived;

        /// <summary>
        /// Data fetched from client side and data prepared to send back to client from server as response
        /// </summary>
        private ClientDataReceivedEventArgs Arguments = null;
        #endregion

        #region jQuery required flags

        private bool isCoreRequired = true;

        /// <summary>
        /// Is jQuery core required?
        /// </summary>
        public bool IsCoreRequired
        {
            get
            {
                return isCoreRequired;
            }
        }
        #endregion

        #region fields & properties

        /// <summary>
        /// Script name to call in the case of successfull callback execution
        /// </summary>
        [Browsable(true)]
        [Category("Callback Handlers")]
        [DefaultValue("onSuccessfullClientHandler")]
        [Description("Script name to call in the case of successfull callback execution")]
        public string OnSuccessfullClientHandler
        {
            get
            {
                return "onSuccessfullClientHandler";
            }
        }

        /// <summary>
        /// Script name to call in the case of unsuccessfull callback execution
        /// </summary>
        [Browsable(true)]
        [Category("Callback Handlers")]
        [DefaultValue("onErrorClientHandler")]
        [Description("Script name to call in the case of unsuccessfull callback execution")]
        public string OnErrorClientHandler
        {
            get
            {
                return "onErrorClientHandler";
            }
        }

        /// <summary>
        /// Client Callback wrapper to be used on the client side to do server method call,  this function will be take one parameter ARG of string type
        /// </summary>
        [Browsable(true)]
        [Category("Callback Handlers")]
        [DefaultValue("serverCall")]
        [Description("Client Callback wrapper to be used on the client side to do server method call,  this function will be take one parameter ARG of string type")]
        public string ServerCallFunctionName
        {
            get
            {
                return "ajaxRequest";
            }
        }
        #endregion

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            this.ClientDataReceived += new EventHandler<ClientDataReceivedEventArgs>(AjaxWebModule_ClientDataReceived);
        }
        
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            #region server callback function registration, here we are wrapping WebFormdoCallback function to our ServerCallFunctionName function

            if (!this.Page.ClientScript.IsClientScriptBlockRegistered(this.Page.GetType(), "AjaxRequestWebFormDoCallbackScript"))
            {
                string webFormDoCallbackScript = this.Page.ClientScript.GetCallbackEventReference("control_id", "serialized_request", "localOnSuccessHandler", null, "localOnErrorHandler", true);
                string serverCallScript = "function " + this.ServerCallFunctionName + "(control_id, arg, localOnSuccessHandler, localOnErrorHandler){" +
                    "\r\n" +
                    "var serialized_request = $.JSON.encode(arg);\r\n" +
                    "if (typeof(control_id) == 'undefined' || control_id == null)\r\n" +
                    String.Format("{{ control_id='{0}';}}\r\n", this.UniqueID) +
                    webFormDoCallbackScript +
                    ";\n}\n";

                this.Page.ClientScript.RegisterClientScriptBlock(this.Page.GetType(), "AjaxRequestWebFormDoCallbackScript", serverCallScript, true);
            }
            #endregion

            #region WebformDoCallback script registration

            //  fix of desribed here http://www.codeproject.com/KB/aspnet/pendingcallbacks.aspx problem

            //  WebForm_CallbackComplete_SyncFixed function registration
            string callbackCompleteFixScriptName = "WebForm_CallbackComplete_SyncFixed";
            string callbackCompleteFixScript = @"
            function WebForm_CallbackComplete_SyncFixed() {
              // the var statement ensure the variable is not global
                 for (var i = 0; i < __pendingCallbacks.length; i++) {
                    callbackObject = __pendingCallbacks[i];
                    if (callbackObject && callbackObject.xmlRequest && 
			            (callbackObject.xmlRequest.readyState == 4)) {
                        
                        if (!__pendingCallbacks[i].async) { 
                            __synchronousCallBackIndex = -1;
                        }
                        __pendingCallbacks[i] = null;
                        var callbackFrameID = '__CALLBACKFRAME' + i;
                        var xmlRequestFrame = document.getElementById(callbackFrameID);
                        if (xmlRequestFrame) {
                            xmlRequestFrame.parentNode.removeChild(xmlRequestFrame);
                        }
                        WebForm_ExecuteCallback(callbackObject);
                    }
                }
            }
            ";
            if (!this.Page.ClientScript.IsClientScriptBlockRegistered(this.Page.GetType(), callbackCompleteFixScriptName))
            {
                this.Page.ClientScript.RegisterClientScriptBlock(this.Page.GetType(), callbackCompleteFixScriptName, callbackCompleteFixScript, true);
            }



            //  here we are replacing WebForm_CallbackComplete function to WebForm_CallbackComplete_SyncFixed function
            string onloadScriptName = "pageload_callback_complete_fix";
            string onloadScript = @"
            if (typeof (WebForm_CallbackComplete) == 'function') {
                WebForm_CallbackComplete = WebForm_CallbackComplete_SyncFixed;
            }
            ";

            if (!this.Page.ClientScript.IsStartupScriptRegistered(callbackCompleteFixScriptName))
            {
                this.Page.ClientScript.RegisterStartupScript(this.GetType(), onloadScriptName, onloadScript, true);
            } 
            #endregion

            #region scripts registration
            ClientScriptManager scriptManager = this.Page.ClientScript;

            scriptManager.RegisterClientScriptInclude("ajax_module_json", scriptManager.GetWebResourceUrl(typeof(AjaxAvailableUserControl), "AjaxAvailableUserControls.Library.Scripts.jquery-json.js"));
            scriptManager.RegisterClientScriptInclude("ajax_module_core", scriptManager.GetWebResourceUrl(typeof(AjaxAvailableUserControl), "AjaxAvailableUserControls.Library.Scripts.core.js"));
            #endregion
        }

        #region ICallbackEventHandler Members

        public string GetCallbackResult()
        {
            //  all operations are done, now we can send the result to client
            if (this.Arguments != null)
            {
                return this.Arguments.ServerResponse;
            }
            else
                return "";
        }

        public void RaiseCallbackEvent(string eventArgument)
        {
            this.OnClientArgumentReceived(eventArgument);
        }

        #endregion

        #region event handlers

        public virtual void OnClientArgumentReceived(string eventArgument)
        {
            if (this.ClientDataReceived != null)
            {
                this.Arguments = new ClientDataReceivedEventArgs
                {
                    ClientData = eventArgument
                };

                this.ClientDataReceived(this, this.Arguments);
            }

        }

        /// <summary>
        /// Default callback handler from client side
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AjaxWebModule_ClientDataReceived(object sender, ClientDataReceivedEventArgs e)
        {
            this.ProcessRequest(e);
        }
        #endregion

        #region logic methods
        
        /// <summary>
        /// Processes request from the client side, call approriate server method inside current user control, creates response object and wrapps it to string value;
        /// Then the result will be send to client as string value
        /// </summary>
        /// <param name="e"></param>
        private void ProcessRequest(ClientDataReceivedEventArgs e)
        {
            e.Cancel = false;
            AjaxRequestParameters parameters = this.ParseParameters(e.ClientData);


            //  server method call
            AjaxResponse response = this.MethodInvoke(parameters);

            //  Now after server method call we can serialize AjaxResponse object to string and send it to client
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            e.ServerResponse = serializer.Serialize(response);
            
        }

        /// <summary>
        /// Invokes server method by his name
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        private AjaxResponse MethodInvoke(AjaxRequestParameters parameters)
        {
            AjaxResponse response = null;
            bool methodFound = false;

            try
            {
                MethodInfo[] MIs = this.GetType().GetMethods(BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance);

                foreach (MethodInfo mi in MIs)
                {
                    if (String.Compare(parameters.MethodName, mi.Name, StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        //  here we are checking returned value type
                        if (mi.ReturnParameter.ParameterType.Equals(typeof(AjaxResponse)))
                        {
                            methodFound = true;

                            //  all values by default equal null
                            object[] parameterValues = new object[mi.GetParameters().Length];
                            if (parameters.Parameters != null &&
                                parameters.Parameters.Count > 0)
                            {
                                for (int i = 0; i < parameterValues.Length; i++)
                                {
                                    parameterValues[i] = null;
                                }   //  for (int i = 0; i < parameterValues.Length; i++ )

                                //  gathering the parameters
                                int index = 0;
                                foreach (var pi in mi.GetParameters())
                                {

                                    if (parameters.Parameters.ContainsKey(pi.Name))
                                    {
                                        parameterValues[index] = parameters.Parameters[pi.Name];
                                    }
                                    index++;
                                }   //  foreach (var pi in mi.GetParameters())
                            }   //  if (parameters.Parameters != null && parameters.Parameters.Count > 0)

                            //  server method call
                            response = (AjaxResponse)mi.Invoke(this, parameterValues);

                        }   //  if (mi.ReturnParameter.GetType().Equals(typeof(AjaxResponse<object>)))
                    }
                }

            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw ex.InnerException;

                throw;
            }

            if (!methodFound)
                throw new MethodAccessException(String.Format("The method {0} with ajax signature 'public AjaxResponse MethodName(parametrs)' not found", parameters.MethodName));
            return response;
        }

        /// <summary>
        /// Deserializes string given from the client side to object of AjaxRequestParameters type
        /// </summary>
        /// <returns></returns>
        private AjaxRequestParameters ParseParameters(string clientData)
        {
            AjaxRequestParameters parameters = null;
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(AjaxRequestParameters));
            using (MemoryStream ms = new MemoryStream(Encoding.Default.GetBytes(clientData)))
            {
                parameters = (AjaxRequestParameters)ser.ReadObject(ms);
            }

            return parameters;
        }

        #endregion
    }
}
